package com.niiwoo.civet.trade.service.local.newRefund.penalty;

import com.niiwoo.civet.base.enums.DeadlineUnitEnum;
import com.niiwoo.civet.base.enums.FeeTypeEnum;
import com.niiwoo.civet.trade.constant.TradeMqConstant;
import com.niiwoo.civet.trade.dao.entity.*;
import com.niiwoo.civet.trade.dao.mapper.*;
import com.niiwoo.civet.trade.dto.newRefund.PenaltyUpdaterDTO;
import com.niiwoo.civet.trade.dto.newRefund.ReceiveFeeDTO;
import com.niiwoo.civet.trade.service.local.newRefund.refundDeal.core.FeeCalcForReceiveService;
import com.niiwoo.civet.trade.service.local.refund.CommonLocalService;
import com.niiwoo.civet.trade.service.local.refund.RefundStatusHandlerService;
import com.niiwoo.tripod.base.utils.DateUtils;
import com.niiwoo.tripod.lanmao.enums.BusinessTypeEnum;
import com.niiwoo.tripod.service.component.SnowflakeIdWorker;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;

/**
 * Created by bincun on 2018/4/24.
 */
@Service
@Slf4j
public class PenaltyHandlerJobService {
    @Autowired
    private ProjectBorrowerSummaryMapperExt projectBorrowerSummaryMapperExt;
    @Autowired
    private ProjectBorrowerPlanNewMapperExt projectBorrowerPlanNewMapperExt;
    @Autowired
    private ProjectMapperExt projectMapperExt;
    @Autowired
    private ProjectBorrowerPlanDetailMapperExt projectBorrowerPlanDetailMapperExt;
    @Autowired
    private ProjectInvestorPlanMapperExt projectInvestorPlanMapperExt;
    @Autowired
    private ProjectGuaranteePlanMapperExt projectGuaranteePlanMapperExt;
    @Autowired
    private PenaltyUpdateFailRecordMapperExt penaltyUpdateFailRecordMapperExt;
    @Autowired
    private TradeConfigMapperExt tradeConfigMapperExt;
    @Autowired
    private CommonLocalService commonLocalService;
    @Autowired
    private FeeCalcForReceiveService feeCalcForReceiveService;
    @Autowired
    private PenaltyRateService penaltyRateService;
    @Autowired
    private SnowflakeIdWorker snowflakeIdWorker;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private RefundOrderMapperExt refundOrderMapperExt;
    @Autowired
    private CurrentDateMapperExt currentDateMapperExt;
    @Autowired
    private RefundStatusHandlerService refundStatusHandlerService;
    private static final String SETTING_SYSTEM_DATETIME = "settingSystemDatetime";


    /**
     * 日切处理
     * 日切后自动触发罚息更新
     */
    @Transactional
    public void cutDayForOverduePenalty() {
        CurrentDate dataObj = currentDateMapperExt.selectByConfigKey(SETTING_SYSTEM_DATETIME);
        log.info("cutDay 定时日切");
        CurrentDate updateRecord = new CurrentDate();
        updateRecord.setId(dataObj.getId());
        Date current = new DateTime(dataObj.getCurrentDate()).plusDays(1).toDate();
        updateRecord.setCurrentDate(current);
        updateRecord.setUpdateTime(new Date());
        currentDateMapperExt.updateByPrimaryKeySelective(updateRecord);

        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
            @Override
            public void afterCommit() {
                super.afterCommit();
                log.info("日切提交成功，开始更新罚息");
                PenaltyHandlerJobService penaltyHandlerJobService = (PenaltyHandlerJobService) AopContext.currentProxy();
                penaltyHandlerJobService.calcPenaltyForProjectForJob(current);
            }
        });
    }

    /**
     * 手动更新标的罚息
     */
    @Transactional
    public void calcPenaltyForProjectForWeb(Long projectId) {
        CurrentDate dataObj = currentDateMapperExt.selectByConfigKey(SETTING_SYSTEM_DATETIME);
        Date current = dataObj.getCurrentDate();
        if (projectId != null && projectId == -1){//处理非正常状态标的（138个）
            log.info("处理非正常状态标的（138个）");
            refundStatusHandlerService.handle();
            return;
        }
        if (StringUtils.isEmpty(projectId)){
            log.info("标的号为空，手动更新全量跑罚息");
            PenaltyHandlerJobService penaltyHandlerJobService = (PenaltyHandlerJobService) AopContext.currentProxy();
            penaltyHandlerJobService.calcPenaltyForProjectForJob(current);
        }else {
            log.info("手动更新单个标的罚息 projectId={}",projectId);
            calcPenaltyForProjectForJobComsumer(projectId,dataObj.getCurrentDate(),this.getIsInprojectPeriodStr());
        }
    }

    private Integer getIsInprojectPeriodStr(){
        String isInProjectPeriodStr = tradeConfigMapperExt.selectTradeConfigByKey("penaltyJob.isInProjectPeriod");
        Integer isInProjectPeriod = 0;//默认强制只跑合同期内
        if (org.apache.commons.lang.StringUtils.isNumeric(isInProjectPeriodStr)){
            isInProjectPeriod = Integer.parseInt(isInProjectPeriodStr);
        }
        return isInProjectPeriod;
    }

    @Transactional( propagation = Propagation.NOT_SUPPORTED)
    public void calcPenaltyForProjectForJob(Date currentDate){
        log.info("罚息开始处理,currentDate={}",DateUtils.format(currentDate,DateUtils.DATE_FORMAT));
        List<Long> projectIdListOverdue =  projectBorrowerSummaryMapperExt.queryOverdueProjectIdList();
        log.info("projectIdListOverdue={}",projectIdListOverdue.size());
        if (!CollectionUtils.isEmpty(projectIdListOverdue)) {
            log.info("4:00分罚息更新,projectIdList.size={}", projectIdListOverdue.size());
            for (Long projectId : projectIdListOverdue) {
                if (projectId != null) {
                    PenaltyUpdaterDTO penaltyUpdaterDTO = new PenaltyUpdaterDTO();
                    penaltyUpdaterDTO.setProjectId(projectId);
                    penaltyUpdaterDTO.setCurrentDate(currentDate);
                    penaltyUpdaterDTO.setStep(this.getIsInprojectPeriodStr());
                    rabbitTemplate.convertAndSend(TradeMqConstant.AUTO_PENALTY_UPDATE_EXCHANGE, TradeMqConstant.AUTO_PENALTY_UPDATE_ROUTE, penaltyUpdaterDTO);
                }
            }
        }
        log.info("罚息批量更新队列消息发送结束,待更新数据量{},currentDate={}",projectIdListOverdue.size(),DateUtils.format(currentDate,DateUtils.DATE_FORMAT));
    }


    /**
     * 罚息更新监听者
     * @param projectId
     * @param currentDate
     * @throws IOException
     */
    @Transactional
    public void calcPenaltyForProjectComsumer(Long projectId,Date currentDate,Integer step) {
        log.info("autoPenaltyUpdate.init---projectId={}",projectId);

        int handingNum = refundOrderMapperExt.selectInProgressOrderCount(projectId);
        if (handingNum>0){
            throw new RuntimeException("标的正在还款中,不更新罚息,projectId="+projectId);
        }
        //调用本地接口接口
        PenaltyHandlerJobService penaltyHandlerJobService = (PenaltyHandlerJobService) AopContext.currentProxy();
        Project project = projectMapperExt.selectByPrimaryKeyForUpdate(projectId);
        Assert.notNull(project,"标的不存在,projectId="+projectId);
        //Boolean isCriOverDue = ProjectSubStatusEnum.REPAYING_BAD.equals(ProjectSubStatusEnum.enumOf(project.getSubStatus()));
        //标的最后还款日
        Date lastRefundDate = projectBorrowerPlanNewMapperExt.selectLastRefundDayByProjectId(projectId);
        BigDecimal refundingPeanaltyFee = BigDecimal.ZERO;//借款人待还罚息上限
        Date compareDate = null;

        /**
         * step 0 :
         所有待收罚息清0，即设置【应收罚息】 = 已收罚息
         所有罚息起始日设置为标的最后还款日 （需求）
         所有初始罚息重置为【已收罚息】；

         step 1 :
         计算合同期外罚息   【针对目前已处理合同期外的标的，我们本身已经可以计罚息了，把这一部分罚息计算出来保存，合同期内罚息在此不作任何处理】

         step 2：
         上线后，只会走一步骤的处理逻辑（step0,setp1）仅用于9月30号处理数据
         逾期但是在合同期内：
         9月30号之前满标，无需处理
         9月30号之后满标，根据实际情况计算最大罚息并分配给债权人
         逾期并且超过合同期：
         从合同期截止日开始算罚息，日罚息利率万6，上不封顶 （如提高罚息利率，则罚息年化利率可能超过24%，需要重新发布升级）
         */

        log.info("罚息JOB步骤，step={},projectId={}",step,projectId);
        // step = 0
        if (step == 0){
            refundingPeanaltyFee = BigDecimal.ZERO;
            compareDate = currentDate;
        }
        //step=1
        else if (step == 1) {
            if (currentDate.after(lastRefundDate)) {
                //待还罚息(合同期外,有罚息)
                //refundingPeanaltyFee = penaltyHandlerJobService.getRefundingPenaltyFeeForOverLastRefundDate(project, lastRefundDate, currentDate);
                refundingPeanaltyFee = new BigDecimal(Integer.MAX_VALUE).setScale(2,BigDecimal.ROUND_DOWN);
                log.info("合同期外待还罚息={},projectId={}", refundingPeanaltyFee,projectId);
                compareDate = lastRefundDate;
            } else {
                return;
            }
        }
        //step=2
        else if (step == 2){
            if (!currentDate.after(lastRefundDate)) {
                if (project.getFullSuccessTime().after(DateUtils.getDateFromCalender(2018,9,28,23,59,59))) {
                    //待还罚息(合同期内)
                    refundingPeanaltyFee = penaltyHandlerJobService.getRefundingPenaltyFee(project);
                    log.info("合同期内待还罚息={},projectId={}", refundingPeanaltyFee,projectId);
                }else {
                    log.info("9月30之前满标标的合同期内免罚息,projectId={}",projectId);
                    return;
                }
            }else {
                //待还罚息(合同期外) 罚息无上限
                refundingPeanaltyFee = new BigDecimal(Integer.MAX_VALUE).setScale(2,BigDecimal.ROUND_DOWN);
                log.info("合同期外待还罚息={},projectId={}",refundingPeanaltyFee,projectId);
            }
        }
        // (先处理回款计划，再处理还款计划)
        penaltyHandlerJobService.calcPenaltyForProjectForPlan(compareDate,step, projectId, penaltyRateW6, refundingPeanaltyFee, currentDate,lastRefundDate);
    }

    /**
     * 合同期外罚息处理
     * @param projectId
     * @param currentDate
     * @param lastRefundDate
     */
    @Transactional
    public void handOrinalPenaltyFeeForOverdueOneDay(Long projectId,Date currentDate,Date lastRefundDate){

        List<ProjectInvestorPlan> projectInvestorPlanList = projectInvestorPlanMapperExt.selectNeedReceiveList(projectId, lastRefundDate);
        for (ProjectInvestorPlan projectInvestorPlan : projectInvestorPlanList) {
            ProjectInvestorPlan projectInvestorPlanUpdater = new ProjectInvestorPlan();
            if (projectInvestorPlan.getPenaltyBeginDate().before(currentDate)) {
                projectInvestorPlanUpdater.setId(projectInvestorPlan.getId());
                projectInvestorPlanUpdater.setPenaltyBeginDate(currentDate);
                projectInvestorPlanUpdater.setOrgPenaltyFee(projectInvestorPlan.getTotalPenaltyFee());
                projectInvestorPlanMapperExt.updateByPrimaryKeySelective(projectInvestorPlanUpdater);
            }
        }

        List<ProjectGuaranteePlan> projectGuaranteePlans = projectGuaranteePlanMapperExt.selectNeedReceiveList(projectId,lastRefundDate);
        for (ProjectGuaranteePlan guaranteePlan : projectGuaranteePlans) {
            ProjectGuaranteePlan projectGuaranteePlanUpdater = new ProjectGuaranteePlan();
            if (guaranteePlan.getPenaltyBeginDate().before(currentDate)) {
                projectGuaranteePlanUpdater.setId(guaranteePlan.getId());
                projectGuaranteePlanUpdater.setPenaltyBeginDate(currentDate);
                projectGuaranteePlanUpdater.setOrigianlPenalty(guaranteePlan.getTotalPenaltyFee());
                projectGuaranteePlanMapperExt.updateByPrimaryKeySelective(projectGuaranteePlanUpdater);
            }
        }
    }



    public void calcPenaltyForProjectForJobComsumer(Long projectId,Date currentDate,Integer step) {
         PenaltyHandlerJobService penaltyHandlerJobService = (PenaltyHandlerJobService)AopContext.currentProxy();
        try {
            penaltyHandlerJobService.calcPenaltyForProjectComsumer(projectId,currentDate, step);
        }catch (Exception e){
            log.error("更新罚息失败,标的号{}，日切{}",projectId,currentDate,e);
            PenaltyUpdateFailRecord penaltyUpdateFailRecord = penaltyUpdateFailRecordMapperExt.queryFailRecordByProjectId(currentDate,projectId);
            if (penaltyUpdateFailRecord == null) {
                PenaltyUpdateFailRecord record = new PenaltyUpdateFailRecord();
                record.setId(snowflakeIdWorker.nextId());
                record.setCurrentDate(currentDate);
                record.setProjectId(projectId);
                record.setCreateTime(new Date());
                penaltyUpdateFailRecordMapperExt.insertSelective(record);
            }
        }
    }

    /**
     * 罚息更新异常标的重新更新
     */
    public void repeatUpdatePenaltyFee(){
        Date currentDate = commonLocalService.getCurrentDate();
        List<PenaltyUpdateFailRecord> penaltyUpdateFailRecords = penaltyUpdateFailRecordMapperExt.queryNotHandlerPenaltyFee(currentDate);
        log.info("本次补偿更新标的列表数量{}",penaltyUpdateFailRecords.size());
        PenaltyHandlerJobService penaltyHandlerJobService = (PenaltyHandlerJobService)AopContext.currentProxy();
        for (PenaltyUpdateFailRecord penaltyUpdateFailRecord : penaltyUpdateFailRecords) {
            try {
                penaltyHandlerJobService.calcPenaltyForProjectComsumer(penaltyUpdateFailRecord.getProjectId(),penaltyUpdateFailRecord.getCurrentDate(),this.getIsInprojectPeriodStr());
                PenaltyUpdateFailRecord penaltyUpdateFailRecordUpdater = new PenaltyUpdateFailRecord();
                penaltyUpdateFailRecordUpdater.setId(penaltyUpdateFailRecord.getId());
                penaltyUpdateFailRecordUpdater.setHandleStatus(Byte.valueOf("2"));//成功
                penaltyUpdateFailRecordMapperExt.updateByPrimaryKeySelective(penaltyUpdateFailRecordUpdater);
            }catch (Exception e ){
                log.error("重新更新标罚息失败,标的号={}",penaltyUpdateFailRecord.getProjectId(),e);
            }
        }

    }


    /**
     * 剩余待还罚息(合同期内)
     * @param project
     * @return
     */
    private final BigDecimal penaltyRateW6 = new BigDecimal("0.0006");
    public BigDecimal getRefundingPenaltyFee(Project project){
        Long projectId = project.getProjectId();
        // 已还罚息
        BigDecimal penaltyFeeRefunded = projectBorrowerPlanDetailMapperExt.queryRefunedPenalty(projectId);
        //  利息
        BigDecimal totalInterest = projectBorrowerPlanDetailMapperExt.selectTotalInterestByProjectId(projectId);
        //利息+平台管理费+担保服务费+咨询服务费
        BigDecimal feeTotal = totalInterest
                .add(project.getConsultingFee())
                .add(project.getGuaranteeFee())
                .add(project.getManageFee())
                .add(project.getBeforeSeekAdviceServiceFee())
                .add(project.getBeforeManageFee() == null ? BigDecimal.ZERO : project.getBeforeManageFee());
        //多少年
        BigDecimal deadlineRate = DeadlineUnitEnum.BY_MONTH.getDeadlineUnit().equals(project.getDeadlineUnit())?
                new BigDecimal(project.getDeadline()).divide(new BigDecimal(12),6,BigDecimal.ROUND_DOWN):new BigDecimal(project.getDeadline()).divide(new BigDecimal(365),6,BigDecimal.ROUND_DOWN);

        //剩余可收取最大罚息
        BigDecimal maxPenaltyFee  = new BigDecimal("0.36").multiply(project.getContractAmount()).multiply(deadlineRate).setScale(2,BigDecimal.ROUND_DOWN).subtract(feeTotal);

        log.info("计算最大罚息maxPenaltyFee={},projectId={},deadlineRate={},penaltyFeeRefunded={}",maxPenaltyFee,projectId,deadlineRate,penaltyFeeRefunded);
        return maxPenaltyFee.subtract(penaltyFeeRefunded).compareTo(BigDecimal.ZERO)>0? maxPenaltyFee.subtract(penaltyFeeRefunded):BigDecimal.ZERO;
    }


    /**
     * 更新还款罚息
     * job服务调用
     * @param projectId
     * @param penaltyRate
     * @param refundingPenaltyFee
     * @param currentDate
     */
    @Transactional
    public void calcPenaltyForProjectForPlan(Date compareDate,Integer step,Long projectId,BigDecimal penaltyRate,BigDecimal refundingPenaltyFee,Date currentDate,Date lastRefundDate){


        //逾期标的才更新
        List<ReceiveFeeDTO> receiveFeeDTOList = feeCalcForReceiveService.getReceiveFeeDTOList(projectId, refundingPenaltyFee, compareDate, currentDate, penaltyRate);
        Map<Integer, BigDecimal> penaltyFeeMap = new HashMap<>();

        for (ReceiveFeeDTO receiveFeeDTO : receiveFeeDTOList) {
            Integer period = receiveFeeDTO.getPeriod();
            BigDecimal penaltyFee = penaltyFeeMap.get(period);
            if (penaltyFee == null) {
                penaltyFeeMap.put(period, receiveFeeDTO.getPenaltyFee());
            } else {
                penaltyFeeMap.put(period, penaltyFee.add(receiveFeeDTO.getPenaltyFee()));
            }
            if (BusinessTypeEnum.REPAYMENT.equals(receiveFeeDTO.getBusinessTypeEnum())){
                ProjectInvestorPlan planUpdater = new ProjectInvestorPlan();
                planUpdater.setId(receiveFeeDTO.getPlanId());
                BigDecimal totalPenaltyFee = receiveFeeDTO.getPenaltyFee().add(receiveFeeDTO.getReceivedPenaltyFee());
                planUpdater.setTotalPenaltyFee( totalPenaltyFee );
                if (step == 0 ){
                    planUpdater.setPenaltyBeginDate(lastRefundDate);
                    planUpdater.setOrgPenaltyFee(totalPenaltyFee);
                }
                projectInvestorPlanMapperExt.updateByPrimaryKeySelective(planUpdater);
            }else if (BusinessTypeEnum.COMPENSATORY_REPAYMENT.equals(receiveFeeDTO.getBusinessTypeEnum())){
                ProjectGuaranteePlan planUpdater = new ProjectGuaranteePlan();
                planUpdater.setId(receiveFeeDTO.getPlanId());
                BigDecimal totalPenaltyFee = receiveFeeDTO.getPenaltyFee().add(receiveFeeDTO.getReceivedPenaltyFee());
                planUpdater.setTotalPenaltyFee( totalPenaltyFee );
                if (step == 0 ){
                    planUpdater.setPenaltyBeginDate(lastRefundDate);
                    planUpdater.setOrigianlPenalty(totalPenaltyFee);
                }
                projectGuaranteePlanMapperExt.updateByPrimaryKeySelective(planUpdater);
            }
        }

        if (penaltyFeeMap.keySet().size() == 0){
            log.info("期数为空,projectId={}",projectId);
            return;
        }
        //合同期外标的处理，把所有初始罚息置为0，罚息起始日设置为(标的到期日)
        if ( (step == 1 && currentDate.after(lastRefundDate))
                || (step == 2 && currentDate.compareTo(lastRefundDate) == 0)) {
            log.info("合同期外第一天，维护初始罚息，projectId={}",projectId);
            try {
                this.handOrinalPenaltyFeeForOverdueOneDay(projectId, currentDate, lastRefundDate);
            }catch (Exception e){
                log.error("标的超过合周期最后一天，初始罚息维护异常projectId={},currentDate={}",projectId,currentDate);
                throw e;
            }
        }
        List<ProjectBorrowerPlanNew> projectBorrowerPlanNewList = projectBorrowerPlanNewMapperExt.selectByProjectIdAndPeriods(projectId, new ArrayList<>(penaltyFeeMap.keySet()));
        Assert.notNull(projectBorrowerPlanNewList,"还款计划不可空,projectId="+projectId);
        for (ProjectBorrowerPlanNew projectBorrowerPlanNew : projectBorrowerPlanNewList) {
            ProjectBorrowerPlanDetail planDetail = projectBorrowerPlanDetailMapperExt.queryProjectBorrowerPlanDetailByPlanIdAndFeeType(projectBorrowerPlanNew.getId(), FeeTypeEnum.PENALTY.getValue());
            Integer period = projectBorrowerPlanNew.getPeriod();
            if (planDetail == null) {
                ProjectBorrowerPlanDetail initPenaltyDetail = new ProjectBorrowerPlanDetail();
                initPenaltyDetail.setId(snowflakeIdWorker.nextId());
                initPenaltyDetail.setMoney(penaltyFeeMap.get(period));
                initPenaltyDetail.setUpdateTime(new Date());
                initPenaltyDetail.setCreateTime(new Date());
                initPenaltyDetail.setFeeType(FeeTypeEnum.PENALTY.getValue());
                initPenaltyDetail.setPlanId(projectBorrowerPlanNew.getId());
                initPenaltyDetail.setRefundMoney(BigDecimal.ZERO);
                initPenaltyDetail.setProjectId(projectId);
                initPenaltyDetail.setUserId(projectBorrowerPlanNew.getUserId());
                projectBorrowerPlanDetailMapperExt.insertSelective(initPenaltyDetail);
            } else {
                ProjectBorrowerPlanDetail planDetailUpdater = new ProjectBorrowerPlanDetail();
                planDetailUpdater.setId(planDetail.getId());
                planDetailUpdater.setMoney(penaltyFeeMap.get(period).add(planDetail.getRefundMoney()));
                planDetailUpdater.setUpdateTime(new Date());
                projectBorrowerPlanDetailMapperExt.updateByPrimaryKeySelective(planDetailUpdater);
            }
        }
        ProjectBorrowerSummary summaryUpdater = new ProjectBorrowerSummary();
        summaryUpdater.setProjectId(projectId);
        summaryUpdater.setPenaltyUpdateDate(currentDate);
        projectBorrowerSummaryMapperExt.updateByPrimaryKeySelective(summaryUpdater);
    }

}
